﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using System.IO;
using log4net;
using System.Reflection;
using System.Web.Caching;
using System.Security.Cryptography;

namespace CodeStash.Utils
{
    public class ContentWatcher
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.Name);
        private readonly IDictionary<string, string> contentHashes = new Dictionary<string, string>();
        private readonly IList<FileSystemWatcher> watchers = new List<FileSystemWatcher>();
        private readonly ReaderWriterLockSlim @lock = new ReaderWriterLockSlim();

        public ContentWatcher()
        {
            HttpRuntime.Cache.Add("__ContentWatcher", this, null, 
                DateTime.MaxValue, TimeSpan.Zero, CacheItemPriority.NotRemovable, null);
        }

        public int WatchPath(string path)
        {
            int count = 0;

            Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories).ToList().ForEach(f =>
            {
                if (StoreHash(f)) { count++; }
            });

            SetupWatcher(path);
            return count;
        }

        private string PathToVirtualPath(string filePath, string rootPath)
        {
            var name = filePath.Substring(rootPath.Length, filePath.Length - rootPath.Length);
            return string.Format("/{0}", name).Replace(@"\", "/");
        }

        private bool StoreHash(string fileName)
        {
            try
            {
                using (FileStream file = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (MD5 md5 = new MD5CryptoServiceProvider())
                    {
                        var name = PathToVirtualPath(file.Name, HttpRuntime.AppDomainAppPath);
                        var hash = BitConverter.ToString(md5.ComputeHash(file)).ToLower().Replace("-", string.Empty);

                        @lock.EnterWriteLock();

                        try
                        {
                            contentHashes[name] = hash;
                        }
                        finally
                        {
                            @lock.ExitWriteLock();
                        }

                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                Log.Error(string.Format("Error calculating hash for: {0} - {1}", fileName, e.Message));
                return false;
            }
        }

        private void SetupWatcher(string path)
        {
            FileSystemWatcher watcher = new FileSystemWatcher(path) 
                { 
                    IncludeSubdirectories = true, EnableRaisingEvents = true 
                };
            watcher.Changed += (FileSystemEventHandler)((s, e) => StoreHash(e.FullPath));
            watchers.Add(watcher);
        }

        public bool ContainsKey(string key)
        {
            @lock.EnterReadLock();

            try
            {
                return contentHashes.ContainsKey(key);
            }
            finally
            {
                @lock.ExitReadLock();
            }
        }

        public string this[string fileName]
        {
            get
            {
                @lock.EnterReadLock();

                try
                {
                    return contentHashes[fileName];
                }
                finally
                {
                    @lock.ExitReadLock();
                }
            }
        }
    }
}